﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

using LTorrent.Torrent;
using LTorrent.Extensions;
using System.IO;

namespace LTorrent.Peers
{
    static class DebugMonitor
    {

       public static int Count = 0;
       public static int Reading = 0;
       public static int ReadingProcess = 0;
    }

    class Caller
    {
        //Получаемые поля
       protected List<FilesInf> _filesList;
        protected string _fileName;
        protected UInt64 _length;
        protected UInt32 _pieceLength;
        protected Byte[] _pieces;
        protected Byte[] _info_hash;
        
        protected List<ExtendedPeer> _peers;
        //TODO - получить нормальную строку
        protected String _peer_id = "mBLA01Torrent_Client";

        //Скаченные поля
        protected Byte[] _downloadedPieces;
        //Заблокированные поля
        protected Byte[] _lockedPieces;

        //
        protected String _directoryTempName;

        //Вычисляемые поля
        UInt64 _downloaded;

        //Количество пустых бит
        int _already;

       

        /// <summary>
        /// Инициализирует класс, реализующий p2p
        /// </summary>
        /// <param name="FilesList">Список файлов на закачку</param>
        /// <param name="FileOrDirectoryName">Директория или названия файла(в случае с одним файлом)</param>
        /// <param name="AllLength">Общая длина</param>
        /// <param name="PieceLength">Длина куска</param>
        /// <param name="Pices">SHA1 кусков</param>
        /// <param name="Info_hash">SHA1 info_hash .torrent файла</param>
        /// <param name="Peers">список пиров</param>
        public Caller(List<FilesInf> FilesList, String FileOrDirectoryName, UInt64 AllLength, UInt32 PieceLength, Byte[] Pices, Byte[] Info_hash, List<ExtendedPeer> Peers)
        {   
            //Заполняем поля класса
            _filesList = FilesList;
            _fileName = FileOrDirectoryName;
            _length = AllLength;
            _pieceLength = PieceLength;
            _pieces = Pices;
            _info_hash = Info_hash;
            _peers = Peers;

            //Заполняем массив скаченный частей битами 0
            _downloadedPieces = new Byte[(_pieces.Length/20 + 8 - 1)/8];
            for (int i = 0; i < _downloadedPieces.Length; i++)
                _downloadedPieces[i] = 0;
            _already = (_pieces.Length / 20) % 8;
            for (int i = 0; i < _already; i++)
                _downloadedPieces[_downloadedPieces.Length - 1] = (Byte)( _downloadedPieces[_downloadedPieces.Length - 1] | (1 << i));

            //Заполняем массив заблокированных (на скачке) кусков 0-ми
            _lockedPieces = new Byte[(_pieceLength / 20 + 8 - 1) / 8];
            for (int i = 0; i < _lockedPieces.Length; i++)
                _lockedPieces[i] = 0;
            
            //создаем мьютекс TODO: если не пригодиться - избавиться (был заменен на lock)
            Mutex AccessMutex = new Mutex(false);

            //Дополнительные параметры (свойства) задаем пирам в цикле
            _peers.ForEach(
                X =>
                    {
                        X.CallBackInstance = this;
                        X.OurPeers = _peers;
                        X.Access = AccessMutex;
                        X.PieceLength = PieceLength;
                    }
                ) ;

            //Создаем временное файло-хранилище
            CreateTempDirectory(FileOrDirectoryName);

            //TODO: добавить подсчет скаченной информации (плюшка)
            _downloaded = 0;
        }

        //Создает временную папку
        protected void CreateTempDirectory(String DirectoryName)
        {
            Directory.CreateDirectory(DirectoryName);
            _directoryTempName = DirectoryName;
        }
        
        //Попытаемся послать сообщение
        public void SendHandshakeMessageAndWait()
        {
            Console.WriteLine("Starting to send Handshake messages");

            var NewMessage = new HandshakeMessage(_info_hash, Encoding.ASCII.GetBytes(_peer_id));

            foreach ( var x in _peers)
            {
                try
                {
                    x.TryToSetConnection(NewMessage);
                }
                catch (System.Exception ex)
                {
                    Console.WriteLine("There are some errors with " + x.IP);	
                }
            }
            _isLoaded.WaitOne();

        }

#region  PeersCalledMetodsAndProperties
        protected ManualResetEvent _isLoaded = new ManualResetEvent(false);
        public Boolean IsLoaded
        {
            get { return _isLoaded.WaitOne(new TimeSpan(1)); }
        }
        //счетчик числа рабочих потоков.
        protected int _threadsCount = 50;
        public int ThreadsCount//Для его обработки использовать lock!!!
        {
            get { return _threadsCount; }
            set { _threadsCount = value; }
        }

        //проверяет - пора ли завершать работу
        public bool CheckCompletionEvent()
        {
            Boolean isDownloadComplete = true;
            for (int i = 0; i < _downloadedPieces.Length && isDownloadComplete; i++)
            {
                if (_downloadedPieces[i] != 255) isDownloadComplete = false;
            }
          if (ThreadsCount <= 0 || isDownloadComplete)//TODO может потребоваться проверка на скаченность файла
            {
               _isLoaded.Set();
               return true;
            }
         return false;
        }

        //Сравнивает indo_hash пира и полученный нами из файла.
        public Boolean CompareInfo_hash(IEnumerable<Byte> PeerInfo_hash)
        {
            int i = 0;
            foreach (var x in PeerInfo_hash)
            {
                if (x != _info_hash[i])
                    return false;
                i++;
            }
            return true;
        }



        //Проверяет, существуют ли куски, в которых мы заинтересованны.
        public Boolean CheckAmIntresting(Byte[] PeerPieceMap)
        {
            Boolean Intrest = false;
            for (int i = 0; i < _downloadedPieces.Length-1 && !Intrest; i++)
                for (int j = 0; j < 8; j++)
                {
                    if (((_downloadedPieces[i] >> j) & 1) < ((PeerPieceMap[i] >> j) & 1))
                        Intrest = true;
                }
            return Intrest;
        }

        /// <summary>
        /// Производит поиск индекса куска, который должен скачать определенный сокет,
        /// Ориентируется по карте, предоставленной удаленным пиром.
        /// Вызывать после блокирования доступа
        /// </summary>
        /// <param name="PeerPieceMap">Карта скаченных кусков</param>
        /// <returns>null - пир утратил свою полезность. Число - пир имеет свою полезность, качать определенный кусок</returns>
        public UInt32? FindNewPieceIndex(Byte[] PeerPieceMap)
        {
            Boolean Intrest = false;
            UInt32 Index = 0;
            for (int i = 0; i < _downloadedPieces.Length - 1 && !Intrest; i++)
                for (int j = 0; j < 8; j++)
                {
                    if (((_downloadedPieces[i] >> j) & 1) < ((PeerPieceMap[i] >> j) & 1) && ( ( (_lockedPieces[i] >> j ) & 1) == 0) )
                    {
                        Index = (UInt32)(i * 8 + j);
                        Intrest = true;
                    }
                }
            return Intrest ? (UInt32?)Index : null;
        }

        //Устанавливает блокировку на кусок
        public void SetLock(UInt32 Index)
        {
            _lockedPieces.addPiece(Index);
        }

        //Снимает блокировку с куска TODO: ввести в пирах таймер максимального ожидания куска!!!!
        public void ReleaseLock(UInt32 Index)
        {
            _lockedPieces.addPiece(Index, 0);
        }

        /// <summary>
        /// Логическое завершение скачивания куска. Запускает поиск следующего.
        /// </summary>
        /// <param name="Index"></param>
        /// <returns></returns>
        public void PieceDownloadingComplete(UInt32 Index)
        {
            _downloadedPieces.addPiece(Index);
            //ReleaseLock(Index); - он все равно заблочен. Смысла нет.
        }

        /// <summary>
        /// Записывает скаченный кусок под своим порядковым номером во временную директорию.
        /// </summary>
        /// <param name="PieceCode">Кусок</param>
        /// <param name="Index">Индекс</param>
        public void WriteNewPieceToTempDirectory(Byte[] PieceCode, UInt32 Index)
        {
            using (var PieceStream = new FileStream(_directoryTempName + Index, FileMode.CreateNew))
            {
                using (var PieceBinary = new BinaryWriter(PieceStream))
                {
                    PieceBinary.Write(PieceCode);
                }
            }
        }
#endregion


    }
}
